program Shaders;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  SampleCommon,
  DIrr in '..\..\DSource\DIrr.pas',
  DIrr.GUI in '..\..\DSource\DIrr.GUI.pas',
  DIrr.Color in '..\..\DSource\DIrr.Color.pas',
  DIrr.Rect in '..\..\DSource\DIrr.Rect.pas',
  DIrr.Event in '..\..\DSource\DIrr.Event.pas',
  DIrr.Vector2D in '..\..\DSource\DIrr.Vector2D.pas',
  DIrr.Vector3D in '..\..\DSource\DIrr.Vector3D.pas',
  DIrr.Video in '..\..\DSource\DIrr.Video.pas',
  DIrr.Base in '..\..\DSource\DIrr.Base.pas',
  DIrr.Types in '..\..\DSource\DIrr.Types.pas',
  DIrr.Scene in '..\..\DSource\DIrr.Scene.pas',
  DIrr.Structs in '..\..\DSource\DIrr.Structs.pas',
  DIrr.Aabbox3D in '..\..\DSource\DIrr.Aabbox3D.pas',
  DIrr.Matrix4 in '..\..\DSource\DIrr.Matrix4.pas',
  DIrr.Texture in '..\..\DSource\DIrr.Texture.pas',
  DIrr.Collections in '..\..\DSource\DIrr.Collections.pas',
  DIrr.Consts in '..\..\DSource\DIrr.Consts.pas',
  DIrr.IO in '..\..\DSource\DIrr.IO.pas',
  DIrr.GPU in '..\..\DSource\DIrr.GPU.pas',
  DIrr.RenderServices in '..\..\DSource\DIrr.RenderServices.pas';

type
  TMyShaderCallBack = class(TShaderConstantSetCallBack)
  public
    procedure OnSetConstants(Services: TRendererServices; UserData: S32); override;
  end;

var
  Device: TIrrlichtDevice = nil;
  Driver: TVideoDriver = nil;
  UseHighLevelShaders: Boolean = False;
  UseCgShaders: Boolean = False;
  DriverType: E_DRIVER_TYPE;
  ic: AnsiChar;

  Smgr: TSceneManager;
  GUI: TGUIEnvironment;
  vsFileName, psFileName: AnsiString;
  Log: TLogger;

  GPU: TGPUProgrammingServices;
  NewMaterialType1: S32 = 0;
  NewMaterialType2: S32 = 0;

  mc: TMyShaderCallBack;
  ShadingLanguage: E_GPU_SHADING_LANGUAGE;

  Node: TSceneNode;
  Anim: TSceneNodeAnimator;
  Cam: TCameraSceneNode;
  LastFPS: Integer = -1;
  FPS: Integer;
  CaptionText: String;

{ TMyShaderCallBack }

procedure TMyShaderCallBack.OnSetConstants(Services: TRendererServices;
  UserData: S32);
var
  LDriver: TVideoDriver;
  InvWorld: TMatrix4F;
  WorldViewProj: TMatrix4F;
  lPos: TVector3DF;
  col: TIrrColorF;
  World: TMatrix4F;
  TextureLayerID: S32;
  Cam: TCameraSceneNode;
begin
  LDriver := TMaterialRendererServices(Services).GetVideoDriver;

  // set inverted world matrix
  // if we are using highlevel shaders (the user can select this when
  // starting the program), we must set the constants by name.

  InvWorld := LDriver.GetTransform(ETS_WORLD)^;
  InvWorld.MakeInverse;

  if UseHighLevelShaders then
    Services.SetVertexShaderConstant('mInvWorld', InvWorld.Pointer, 16)
  else
    Services.SetVertexShaderConstant(InvWorld.Pointer, 0, 4);

  // set clip matrix
  WorldViewProj := LDriver.GetTransform(ETS_PROJECTION)^;
  WorldViewProj := WorldViewProj * LDriver.GetTransform(ETS_VIEW)^;
  WorldViewProj := WorldViewProj * LDriver.GetTransform(ETS_WORLD)^;

  if UseHighLevelShaders then
    Services.SetVertexShaderConstant('mWorldViewProj', WorldViewProj.Pointer, 16)
  else
    Services.SetVertexShaderConstant(WorldViewProj.Pointer, 4, 4);

  // set camera position
  Cam := Device.GetSceneManager.GetActiveCamera;
  Cam.GetAbsolutePosition(lPos);

  if UseHighLevelShaders then
    Services.SetVertexShaderConstant('mLightPos', PF32(@lPos), 3)
  else
    Services.SetVertexShaderConstant(PF32(@lPos), 8, 1);

  // set light color

  col.Init(0, 1, 1, 0);

  if UseHighLevelShaders then
    Services.SetVertexShaderConstant('mLightColor', PF32(@col), 4)
  else
    Services.SetVertexShaderConstant(PF32(@col), 9, 1);

  // set transposed world matrix
  World := LDriver.GetTransform(ETS_WORLD)^;
  World := World.GetTransposed;

  if UseHighLevelShaders then
  begin
    Services.SetVertexShaderConstant('mTransWorld', World.Pointer, 16);

    // set texture, for textures you can use both an int and a float setPixelShaderConstant interfaces (You need it only for an OpenGL driver).
    TextureLayerID := 0;
    if UseHighLevelShaders then
      Services.SetPixelShaderConstant('myTexture', PS32(@TextureLayerID), 1);
  end
  else
    Services.SetVertexShaderConstant(World.Pointer, 10, 4);

end;

begin
  try
    { TODO -oUser -cConsole Main : Insert code here }
    DriverType := DriverChoiceConsole;
    if DriverType = EDT_COUNT then
    begin
      ExitCode := 1;
      Exit;
    end;

    // ask the user if we should use high level shaders for this example
    if DriverType in [EDT_DIRECT3D9, EDT_OPENGL] then
    begin
      Writeln('Please press ''y'' if you want to use high level shaders.');
      Readln(ic);

      if ic = 'y' then
      begin
        UseHighLevelShaders := True;
        Writeln('Please press ''y'' if you want to use Cg shaders.');
        Readln(ic);
        if ic = 'y' then
          UseCgShaders := True;
      end;
    end;

    // create device and exit if creation failed
    Device := createDevice(DriverType, [640, 480], 16);
    if Device = nil then
    begin
      ExitCode := 1;   // could not create selected driver.
      Exit;
    end;

    Driver := Device.GetVideoDriver;
    Smgr := Device.GetSceneManager;
    GUI := Device.GetGUIEnvironment;

    // Make sure we don't try Cg without support for it
    if UseCgShaders and (not Driver.QueryFeature(EVDF_CG)) then
    begin
      Writeln('Warning: No Cg support, disabling.');
      UseCgShaders := False;
    end;

    (*
    Now for the more interesting parts. If we are using Direct3D, we want
    to load vertex and pixel shader programs, if we have OpenGL, we want to
    use ARB fragment and vertex programs. I wrote the corresponding
    programs down into the files d3d8.ps, d3d8.vs, d3d9.ps, d3d9.vs,
    opengl.ps and opengl.vs. We only need the right filenames now. This is
    done in the following switch. Note, that it is not necessary to write
    the shaders into text files, like in this example. You can even write
    the shaders directly as strings into the cpp source file, and use later
    addShaderMaterial() instead of addShaderMaterialFromFiles().
    *)
    case DriverType of
      EDT_DIRECT3D8:
      begin
        psFileName := '..\..\media\d3d8.psh';
        vsFileName := '..\..\media\d3d8.vsh';
      end;
      EDT_DIRECT3D9:
      begin
        if UseHighLevelShaders then
        begin
          // Cg can also handle this syntax
          psFileName := '..\..\media\d3d9.hlsl';
          vsFileName := '..\..\media\d3d9.hlsl';  // both shaders are in the same file
        end
        else
        begin
          psFileName := '..\..\media\d3d8.psh';
          vsFileName := '..\..\media\d3d8.vsh';
        end;
      end;
      EDT_OPENGL:
      begin
        if UseHighLevelShaders then
        begin
          if not UseCgShaders then
          begin
            psFileName := '..\..\media\opengl.frag';
            vsFileName := '..\..\media\opengl.vert';
          end
          else
          begin
            // Use HLSL syntax for Cg
            psFileName := '..\..\media\d3d9.hlsl';
            vsFileName := psFileName;   // both shaders are in the same file
          end;
        end
        else
        begin
          psFileName := '..\..\media\opengl.psh';
          vsFileName := '..\..\media\opengl.vsh';
        end;
      end;
    end;

    (*
    In addition, we check if the hardware and the selected renderer is
    capable of executing the shaders we want. If not, we simply set the
    filename string to 0. This is not necessary, but useful in this
    example: For example, if the hardware is able to execute vertex shaders
    but not pixel shaders, we create a new material which only uses the
    vertex shader, and no pixel shader. Otherwise, if we would tell the
    engine to create this material and the engine sees that the hardware
    wouldn't be able to fulfill the request completely, it would not
    create any new material at all. So in this example you would see at
    least the vertex shader in action, without the pixel shader.
    *)
    Log := Device.GetLogger;

    if (not Driver.QueryFeature(EVDF_PIXEL_SHADER_1_1)) and
      (not Driver.QueryFeature(EVDF_ARB_FRAGMENT_PROGRAM_1)) then
    begin
      Log.Log('WARNING: Pixel shaders disabled because of missing driver/hardware support.');
      psFileName := '';
    end;

    if (not Driver.QueryFeature(EVDF_VERTEX_SHADER_1_1)) and
      (not Driver.QueryFeature(EVDF_ARB_VERTEX_PROGRAM_1)) then
    begin
      Log.Log('WARNING: Vertex shaders disabled because of missing driver/hardware support.');
      vsFileName := '';
    end;

    (*
    Now lets create the new materials. As you maybe know from previous
    examples, a material type in the Irrlicht engine is set by simply
    changing the MaterialType value in the SMaterial struct. And this value
    is just a simple 32 bit value, like video::EMT_SOLID. So we only need
    the engine to create a new value for us which we can set there. To do
    this, we get a pointer to the IGPUProgrammingServices and call
    addShaderMaterialFromFiles(), which returns such a new 32 bit value.
    That's all.

    The parameters to this method are the following: First, the names of
    the files containing the code of the vertex and the pixel shader. If
    you would use addShaderMaterial() instead, you would not need file
    names, then you could write the code of the shader directly as string.
    The following parameter is a pointer to the IShaderConstantSetCallBack
    class we wrote at the beginning of this tutorial. If you don't want to
    set constants, set this to 0. The last parameter tells the engine which
    material it should use as base material.

    To demonstrate this, we create two materials with a different base
    material, one with EMT_SOLID and one with EMT_TRANSPARENT_ADD_COLOR.
    *)

    // create materials

    GPU := Driver.GetGPUProgrammingServices;
    if Assigned(GPU) then
    begin
      mc := TMyShaderCallBack.Create;

      // create the shaders depending on if the user wanted high level
      // or low level shaders:

      if UseHighLevelShaders then
      begin
        // Choose the desired shader type. Default is the native
        // shader type for the driver, for Cg pass the special
        // enum value EGSL_CG
        if UseCgShaders then
          ShadingLanguage := EGSL_CG
        else
          ShadingLanguage := EGSL_DEFAULT;

        // create material from high level shaders (hlsl, glsl or cg)
        NewMaterialType1 := GPU.AddHighLevelShaderMaterialFromFiles(
          vsFileName, 'vertexMain', EVST_VS_1_1,
          psFileName, 'pixelMain', EPST_PS_1_1,
          mc, EMT_SOLID, 0, ShadingLanguage);

        NewMaterialType2 := GPU.AddHighLevelShaderMaterialFromFiles(
          vsFileName, 'vertexMain', EVST_VS_1_1,
          psFileName, 'pixelMain', EPST_PS_1_1,
          mc, EMT_TRANSPARENT_ADD_COLOR, 0, ShadingLanguage);
      end
      else
      begin
        // create material from low level shaders (asm or arb_asm)

        NewMaterialType1 := GPU.AddShaderMaterialFromFiles(
          vsFileName, psFileName, mc, EMT_SOLID);

        NewMaterialType2 := GPU.AddShaderMaterialFromFiles(
          vsFileName, psFileName, mc, EMT_TRANSPARENT_ADD_COLOR);
      end;

      mc.Drop;
    end;

    (*
    Now it's time for testing the materials. We create a test cube and set
    the material we created. In addition, we add a text scene node to the
    cube and a rotation animator to make it look more interesting and
    important.
    *)

    // create test scene node 1, with the new created material type 1

    Node := Smgr.AddCubeSceneNode(50);
    Node.SetPosition(Vector3DF(0, 0, 0));
    Node.SetMaterialTexture(0, Driver.GetTexture('..\..\media\wall.bmp'));
    Node.SetMaterialFlag(EMF_LIGHTING, False);
    Node.SetMaterialType(E_MATERIAL_TYPE(NewMaterialType1));

    Smgr.AddTextSceneNode(GUI.GetBuildInFont, PWideChar('PS & VS & EMT_SOLID'), COLOR_FULL, Node);

    Anim := Smgr.CreateRotationAnimator(Vector3DF(0, 0.3, 0));
    Node.AddAnimator(Anim);
    Anim.Drop;

    (*
    Same for the second cube, but with the second material we created.
    *)

    // create test scene node 2, with the new created material type 2

    Node := Smgr.AddCubeSceneNode(50);
    Node.SetPosition(Vector3DF(0, -10, 50));
    Node.SetMaterialTexture(0, Driver.GetTexture('..\..\media\wall.bmp'));
    Node.SetMaterialFlag(EMF_LIGHTING, False);
    Node.SetMaterialFlag(EMF_BLEND_OPERATION, True);
    Node.SetMaterialType(E_MATERIAL_TYPE(newMaterialType2));

    Smgr.AddTextSceneNode(GUI.GetBuildInFont, PWideChar('PS & VS & EMT_TRANSPARENT'), COLOR_FULL, Node);

    Anim := Smgr.CreateRotationAnimator(Vector3DF(0, 0.3, 0));
    Node.AddAnimator(Anim);
    Anim.Drop;

    (*
    Then we add a third cube without a shader on it, to be able to compare
    the cubes.
    *)

    // add a scene node with no shader

    Node := Smgr.AddCubeSceneNode(50);
    Node.SetPosition(Vector3DF(0, 50, 50));
    Node.SetMaterialTexture(0, Driver.GetTexture('..\..\media\wall.bmp'));
    Node.SetMaterialFlag(EMF_LIGHTING, False);

    Smgr.AddTextSceneNode(GUI.GetBuildInFont, PWideChar('NO SHADER'), COLOR_FULL, Node);

    (*
    And last, we add a skybox and a user controlled camera to the scene.
    For the skybox textures, we disable mipmap generation, because we don't
    need mipmaps on it.
    *)

    // add a nice skybox

    Driver.SetTextureCreationFlag(ETCF_CREATE_MIP_MAPS, False);

    Smgr.AddSkyBoxSceneNode(
      Driver.GetTexture('..\..\media\irrlicht2_up.jpg'),
      Driver.GetTexture('..\..\media\irrlicht2_dn.jpg'),
      Driver.GetTexture('..\..\media\irrlicht2_lf.jpg'),
      Driver.GetTexture('..\..\media\irrlicht2_rt.jpg'),
      Driver.GetTexture('..\..\media\irrlicht2_ft.jpg'),
      Driver.GetTexture('..\..\media\irrlicht2_bk.jpg')
      );

    Driver.SetTextureCreationFlag(ETCF_CREATE_MIP_MAPS, True);

    // add a camera and disable the mouse cursor

    Cam := Smgr.AddCameraSceneNodeFPS();
    Cam.SetPosition(Vector3DF(-100, 50, 100));
    Cam.SetTarget(Vector3DF(0, 0, 0));
    Device.GetCursorControl.SetVisible(False);

    (*
    Now draw everything. That's all.
    *)

    while (Device.Run and Assigned(Driver)) do
    begin
      if Device.IsWindowActive then
      begin
        Driver.BeginScene(True, True, IrrColor(0, 0, 0, 255));
        Smgr.DrawAll;
        Driver.EndScene;

        FPS := Driver.GetFPS;
        if LastFPS <> FPS then
        begin
          CaptionText := 'Irrlicht Engine - Vertex and pixel shader example [' + String(Driver.GetName) +
            '] FPS:' + IntToStr(FPS);

          Device.SetWindowCaption(PWideChar(CaptionText));
          LastFPS := FPS;
        end;

      end;
    end;

    Device.Drop;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.
